package springbook.user.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.sql.DataSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.mail.MailSender;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;

import springbook.user.dao.UserDao;
import springbook.user.domain.User;
import springbook.user.domain.User.Level;

import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

import static springbook.user.service.UserServiceImpl.MIN_LOGCOUNT_FOR_SILVER;
import static springbook.user.service.UserServiceImpl.MIN_RECOMMNED_FOR_GOLD;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class UserServiceTest {
	@Autowired
	UserService userService;
	@Autowired
	UserService testUserService;
	List<User> users;
	@Autowired	
	private UserDao userDao;
	@Autowired
	DataSource dataSource;
	@Autowired
	PlatformTransactionManager transactionManager;
	@Autowired
	MailSender mailSender;
	@Autowired 
	ApplicationContext context;
	
	@Before
	public void setUp(){
		users = Arrays.asList(
				new User("test1", "테스트1", "pass1","test@test.net", Level.BASIC, MIN_LOGCOUNT_FOR_SILVER-1, 0),
				new User("test2", "테스트2", "pass2","test@test.net", Level.BASIC, MIN_LOGCOUNT_FOR_SILVER, 0),
				new User("test3", "테스트3", "pass3","test@test.net", Level.SILVER, 60, MIN_RECOMMNED_FOR_GOLD-1),
				new User("test4", "테스트4", "pass4","test@test.net", Level.SILVER, 60, MIN_RECOMMNED_FOR_GOLD),
				new User("test5", "테스트5", "pass5","test@test.net", Level.GOLD, 100, Integer.MAX_VALUE)
		);
	}
	
	@Test
	public void bean(){
		assertThat(this.userService, is(notNullValue()));
	}
	
	/*
	@Test	
	public void upgradeLevels() throws Exception{
		userDao.deleteAll();
		
		for(User user : users){
			userDao.add(user);
		}
		
		userServiceImpl.upgradeLevels();
		
		checkLevelUpgraded(users.get(0), false);
		checkLevelUpgraded(users.get(1), true);
		checkLevelUpgraded(users.get(2), false);
		checkLevelUpgraded(users.get(3), true);
		checkLevelUpgraded(users.get(4), false);		
	}*/
	
	// 고립된 테스트 시작
	@Test	
	public void upgradeLevels() throws Exception{
		UserServiceImpl userServiceImpl = new UserServiceImpl();
		
		MockUserDao mockUserDao = new MockUserDao(this.users);
		userServiceImpl.setUserDao(mockUserDao);
		
		MailSender mailSender = new DummyMailSender();
		userServiceImpl.setMailSender(mailSender);
						
		userServiceImpl.upgradeLevels();
		
		
		
		List<User>	updated = mockUserDao.getUpdated();
		assertThat(updated.size(), is(2));
		checkUserAndLevel(updated.get(0),"test2", Level.SILVER);
		checkUserAndLevel(updated.get(1),"test4", Level.GOLD);
	}
	
	private void checkUserAndLevel(User updated, String expectedId, Level expectedLevel){		
		assertThat(updated.getId(), is(expectedId));
		assertThat(updated.getLevel(), is(expectedLevel));
	}
	//고립된 테스트 끝
	
	@Test
	public void add(){
		
		userDao.deleteAll();
		
		User userWithLevel = users.get(4); //GOLD
		User userWithoutLevel = users.get(0);
		userWithoutLevel.setLevel(null);
		
		userService.add(userWithLevel);
		userService.add(userWithoutLevel);
		
		User userWithLevelRead = userDao.get(userWithLevel.getId());
		User userWithoutLevelRead = userDao.get(userWithoutLevel.getId());
		
		assertThat(userWithLevelRead.getLevel(), is(userWithLevel.getLevel()));
		assertThat(userWithoutLevelRead.getLevel(), is(Level.BASIC));
		
	}
	
	@Test
	@DirtiesContext
	public void upgradeAllOrNothing() throws Exception{
		
		userDao.deleteAll();
		
		for(User user : users) userDao.add(user);
		
		try {
			this.testUserService.upgradeLevels();
			fail("TestUserServiceException expected");
		} catch (TestUserServiceException e) {
			
		}
		
		checkLevelUpgraded(users.get(1), false);
	}
	
	private void checkLevel(User user, Level expectedLevel){
		User userUpdate = userDao.get(user.getId());
		assertThat(userUpdate.getLevel(), is(expectedLevel));
	}
	
	private void checkLevelUpgraded(User user, boolean upgraded){
		User userUpdate = userDao.get(user.getId());
		
		if(upgraded){
			assertThat(userUpdate.getLevel(), is(user.getLevel().nextLevel()));
		}
		else{
			assertThat(userUpdate.getLevel(), is(user.getLevel()));
		}
	}
	
	static class TestUserServiceImpl extends UserServiceImpl {
		private String id = "test1";
						
		protected void upgradeLevel(User user){
			
			if(user.getId().equals(this.id)){
				
				throw new TestUserServiceException();
			}
				super.upgradeLevel(user);	
		}
	}
	
	
	static class TestUserServiceException extends RuntimeException{
		
	}
	
	static class MockUserDao implements UserDao{
		private List<User> users;
		private List<User> updated = new ArrayList();
		
		private MockUserDao(List<User> users){
			this.users = users;
		}
		
		public List<User> getUpdated(){
			return this.updated;
		}
		
		@Override
		public List<User> getAll() {			
			return this.users;
		}

		@Override
		public void update(User user) {
			updated.add(user);			
		}
		
		@Override
		public void add(User user) {throw new UnsupportedOperationException();}

		@Override
		public void deleteAll() {throw new UnsupportedOperationException();}

		@Override
		public User get(String id) {throw new UnsupportedOperationException();}

		@Override
		public int getCount() {throw new UnsupportedOperationException();}
		
	}
}
